package top.xia17.plugins.fast.dynamic.datasource.aop;

import cn.hutool.core.util.StrUtil;
import top.xia17.plugins.fast.common.database.dynamic.DsSwitch;
import top.xia17.plugins.fast.common.spring.aop.AnnotationMethodPoint;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.AbstractPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;

import java.lang.annotation.Annotation;

/**
 * 动态切换数据源AopAdvisor
 * @author xia17
 * @since 2022/5/12 8:54
 */
@Slf4j
public class DynamicSwitchDatasourceAdvisor extends AbstractPointcutAdvisor {

    private final static String DEFAULT_DATASOURCE_NAME = "master";

    private final MethodInterceptor methodInterceptor;
    private final Pointcut pointcut;

    /**
     * 构造器
     * @param methodInterceptor 自定义方法拦截器
     * @param pointcut 自定义切点
     */
    private DynamicSwitchDatasourceAdvisor(MethodInterceptor methodInterceptor , Pointcut pointcut){
        this.methodInterceptor = methodInterceptor;
        this.pointcut = pointcut;
    }

    /**
     * 构造一个可以对标记了指定注解的动态切换数据源的切面
     * 注解支持标记在类上，此时对标记类的父类方法也支持
     * 注解支持标记在方法上的
     * @param dsSwitch 切换数据源的工具
     * @param ds 切换的目标数据源
     * @param annotationType 注解类型
     */
    public DynamicSwitchDatasourceAdvisor(DsSwitch dsSwitch , String ds , Class<? extends Annotation> annotationType) {
        this(new SwitchDatasourceMethodInterceptor(dsSwitch,ds),buildAnnotationPointcut(annotationType));
    }

    /**
     * 构造一个可以对指定切点的动态切换数据源的切面
     * @param dsSwitch 切换数据源的工具
     * @param ds 切换的目标数据源
     * @param pointcut 切点
     */
    public DynamicSwitchDatasourceAdvisor(DsSwitch dsSwitch , String ds , Pointcut pointcut) {
        this(new SwitchDatasourceMethodInterceptor(dsSwitch,ds),pointcut);
    }

    /**
     * 构建一个注解类型的切点
     * 支持标记在类上的注解类型 ，对标记类的父类方法也支持
     * 支持标记在方法上的注解
     * @param annotationType 注解类型
     * @return 切点
     */
    public static Pointcut buildAnnotationPointcut(Class<? extends Annotation> annotationType){
        // 标记在类上的注解类型 ， 对父类的方法也有切面
        Pointcut cpc = new AnnotationMatchingPointcut(annotationType,true);
        // 标记在方法上的注解
        AnnotationMethodPoint mpc = new AnnotationMethodPoint(annotationType);
        return new ComposablePointcut(cpc).union(mpc);
    }

    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }

    @Override
    public Advice getAdvice() {
        return this.methodInterceptor;
    }

    @RequiredArgsConstructor
    static class SwitchDatasourceMethodInterceptor implements MethodInterceptor{

        private final DsSwitch dsSwitch;
        private final String ds;


        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            String current = dsSwitch.peek();
            current = StrUtil.isBlank(current) ? DEFAULT_DATASOURCE_NAME : current;
            if (current.equals(ds)){
                return invocation.proceed();
            }
            boolean isSwitch = false;
            // 添加拦截器
            try {
                dsSwitch.push(ds);
                log.debug("{}方法切换数据源{}",invocation.getMethod().getName(),ds);
                isSwitch = true;
                return invocation.proceed();
            }finally {
                if (isSwitch){
                    // 清除切换的数据源
                    dsSwitch.poll();
                    log.debug("{}方法清除切换数据源，当前数据源{}",invocation.getMethod().getName(),dsSwitch.peek());
                }
            }
        }



    }



}
